package com.ticorp.framework.service.impl;

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sap.mw.jco.IFunctionTemplate;
import com.sap.mw.jco.IRepository;
import com.sap.mw.jco.JCO;
import com.sap.mw.jco.JCO.Client;
import com.sap.mw.jco.JCO.Field;
import com.sap.mw.jco.JCO.FieldIterator;
import com.sap.mw.jco.JCO.Structure;
import com.ticorp.framework.service.BapiExecutionService;
import com.ticorp.framework.service.exception.BapiException;
import com.ticorp.framework.util.DateUtil;
import com.ticorp.salmofood.common.Constantes;
import com.ticorp.salmofood.web.bean.BeanUsuario;

/** 
 * Implementacion de BapiExecutionService. Utiliza un properties para su
 * inicializacion.
 * 
 * @author Lennon Shimokawa
 * 		   Nestor Audante
 */
public class BapiExecutionServiceImpl implements BapiExecutionService {

	private static final String WINDOWS = "Windows";
		
	private final Log logger = LogFactory.getLog(getClass());	
	private static IRepository bapiRepository;
	private static StandardRepositoryManager repositoryManager = null;
	private static boolean initialized = false;

	// Tipos de datos de las BAPIS
	private static final String BCD = "BCD";
	private static final String DATE = "DATE";
	private static final String TIME = "TIME";
	private static final String CHAR = "CHAR";
	private static final String XSTRING = "XSTRING";

	// Parametros de conexion al SAP
	private static String userPRD;
	private static  String passwordPRD;
	private String client;
	private static String user;
	private static String password;	
	private String host;
	private String systemNumber;
	private String language;
	
	private String hostNW_DEV;
	private String clientNW_DEV;	
	private String sysNumberNW_DEV;		

	private String hostNW_PRD;
	private String clientNW_PRD;	
	private String sysNumberNW_PRD;		
	
	private String r3Name;
	private String messageServer;
	private String groupName;
	
	private String osname = null;

	/**
	 * Inicializa el JCO SAP Connector, los parametros son leidos del archivo
	 * bapi.properties que es inyectado mediante Spring.
	 */
	public synchronized void initialize() {
		if (!initialized) {
			try { 
				try {
					osname = System.getProperty("os.name");
				} catch (Exception e) {
					osname = null;
				}
				logger.debug("Inicializando JCO ...");
				getClient();
				getHost();
				getSystemNumber();
				logger.debug("Conectando a: host=" + host + " client="	+ client + " systemNumber="+systemNumber+" R3NAME="+r3Name + " MESSAGESERVER="+messageServer +" GROUPNAME="+groupName);
							
				//RDM 22.09.2008
				repositoryManager = StandardRepositoryManager.getSingleInstance();
				//bapiRepository = repositoryManager.createRepository(client, user, password, language, host, systemNumber);
				
				if (osname!=null && osname.startsWith(WINDOWS)) {
					logger.debug("Se detecto el SO Windows");
					bapiRepository = repositoryManager.createRepository(client, user, password, language, host, systemNumber);
				} else {
					logger.debug("Se detecto el SO diferente de Windows");
					bapiRepository = repositoryManager.createRepository(client, user, password, language, host, systemNumber);
				}

				initialized = true;
			} catch (Exception e) {
				logger.error("Error in JCO initialize.", e);
			}
		}
	}

	/**
	 * 
	 * @return
	 */
	private String getSystemNumber() { 		
		if (osname!=null && osname.startsWith(WINDOWS)) {
			systemNumber = sysNumberNW_DEV;
		} else {
			systemNumber = sysNumberNW_PRD;
		}
		return systemNumber;
	}

	/**
	 * 
	 * @return
	 */ 
	
	private String getHost() {		
		if (osname!=null && osname.startsWith(WINDOWS)) {
			host = hostNW_DEV;
		} else {
			host = hostNW_PRD;
		}
		return host;
	}

	/**
	 * 
	 * @return
	 */
	
	private String getClient() {		
		if (osname!=null && osname.startsWith(WINDOWS)) {
			client = clientNW_DEV;
		} else {
			client = clientNW_PRD;
		}
		return client;
	}


	private IRepository getRepository(){ 
		return bapiRepository;
	}	
	
	
	
	/*
	 * (non-Javadoc)
	 * @see com.ticorp.framework.service.BapiExecutionService#execute(java.lang.String, java.util.Map, boolean)
	 */
	public Map execute(String bapiName, Map importMap, boolean convertVal){		
		logger.info(bapiName + ": inicio "+(new Date()));
		if(logger.isDebugEnabled())
			logger.debug("convertVal: "+convertVal);
		
		JCO.Client client = null;
		Map resultMap = new LinkedHashMap();
		
		try {
			if (importMap != null && importMap.get("usuario") != null) {
				BeanUsuario beanUsuario = (BeanUsuario) importMap.get("usuario");
				client = getUserClient(beanUsuario);
			}
			 else {
				client = getGenericUserClient(user,password);
			}
			
			bapiRepository = getRepository();

			IFunctionTemplate functionTemplate = null;			
			functionTemplate = bapiRepository.getFunctionTemplate(bapiName);				

			if (functionTemplate == null) {
				throw new BapiException("No se encontro la BAPI " + bapiName);
			}

			JCO.Function function = functionTemplate.getFunction();

			// Agregando los ImportParameters obteniendolos del importMap
			JCO.ParameterList importList = function.getImportParameterList();
			if (importList != null && importMap != null) {
				for (FieldIterator importIterator = importList.fields(); importIterator.hasMoreFields();) {
					Field importField = importIterator.nextField();

					// Soporte para estructuras
					if (importField.isStructure()) {
						Structure importFieldStructure = importField.getStructure();
						Map importMapStructure = (Map)importMap.get(importField.getName());
						if (importMapStructure != null) {
							for (int i = 0; i < importFieldStructure.getFieldCount(); i++) {
								Field structureField = importFieldStructure.getField(i);
								Object value = importMapStructure.get(structureField.getName());
								putValueInField(structureField, value);
							}
						}
					}
					// Soporte para elementos de datos
					else {
						Object value = importMap.get(importField.getName());
						putValueInField(importField, value);
					}
				}				
				logger.debug(bapiName + ": importParameterList.toXML="	+ importList.toXML());				
			}

			// Agregando los TableParameters obteniendolos del importMap
			JCO.ParameterList tableParameterList = function.getTableParameterList();
			if (tableParameterList != null && importMap != null) {
				for (FieldIterator tableIterator = tableParameterList.fields(); tableIterator.hasMoreFields();) {
					Field tableField = tableIterator.nextField();
					JCO.Table table = tableParameterList.getTable(tableField.getName());
					if (table != null) {
						List tableList = (List) importMap.get(tableField.getName());
						if (tableList != null) {
							table.clear();

							Map rowMap = null;
							JCO.Record record = new JCO.Record(table.getMetaData());

							// Lleno el TableParameter con el List obtenido
							// desde el importMap
							for (Iterator listIterator = tableList.iterator(); listIterator.hasNext(); ) {
								rowMap = (Map) listIterator.next();
								for (FieldIterator fieldIterator = record.fields(); fieldIterator.hasMoreFields();) {
									Field field = fieldIterator.nextField();
									Object value = rowMap.get(field.getName());
									putValueInField(field, value);
								}
								table.copyFrom(record);
							}
							logger.debug(bapiName	+ ": importTableParameterList.rows="+ table.getNumRows());
							logger.debug(bapiName	+ ": importTableParameterList="	+ table.toXML());							
						}
					}
				}
			}
			
			client.execute(function);
			
			// Se ejecuta la BAPI, se retornan los TableParameter y ExportParameter

			// Agregando los ExportParameters al resultMap
			JCO.ParameterList exportList = function.getExportParameterList();
		
			if (exportList != null) {
				String exportListXML = exportList.toXML();
				for (FieldIterator exportIterator = exportList.fields(); exportIterator.hasMoreFields(); ) {
					Field exportField = exportIterator.nextField();

					// Soporte para estructuras
					if (exportField.isStructure()) {
						Map structureMap = new LinkedHashMap();
						Structure structure = exportField.getStructure();
						for (FieldIterator it = structure.fields(); it.hasNextFields(); ) {
							Field structureField = it.nextField();
							putFieldInMap(structureMap, structureField, convertVal);
						}
						resultMap.put(exportField.getName(), structureMap);
					}

					// Soporte para elementos de datos
					else {
						if(StringUtils.equals(exportField.getTypeAsString(), XSTRING)){
							String xml = StringUtils.substringBetween(exportListXML, "<"+exportField.getName()+">", "</"+exportField.getName()+">");
							resultMap.put(exportField.getName(), xml);
						}else{
							putFieldInMap(resultMap, exportField, convertVal);
						}
					}
				}
				
				logger.debug(bapiName + ": exportParameterList.toXML="	+ exportList.toXML());				
			}

			// Agregando los TableParameters al resultMap
			if (tableParameterList != null) {
				for (FieldIterator tableIterator = tableParameterList.fields(); tableIterator.hasMoreFields(); ) {
					Field tableField = tableIterator.nextField();

					JCO.Table table = tableParameterList.getTable(tableField.getName());
					if (table != null && !table.isEmpty()) {
						List tableList = new ArrayList();
						// Inserto los registros de la tabla en
						// tableList
						do {
							// Lleno los campos del registro
							Map rowMap = new LinkedHashMap();
							for (JCO.FieldIterator e = table.fields(); e.hasMoreElements(); ) {
								JCO.Field column = e.nextField();
								putFieldInMap(rowMap, column, convertVal);
							}
							tableList.add(rowMap);
						} while (table.nextRow());

						resultMap.put(tableField.getName(), tableList);
					}
				}
				
				logger.debug(bapiName + ": exportTableParameterList.toXML="+ tableParameterList.toXML());				
			}			
		} 
		catch (Exception e) {
			logger.error(bapiName + ": " + e.getMessage(), e);
			throw new BapiException(e.getMessage(), e);
		} 
		finally {
			client.disconnect();
			if (logger.isDebugEnabled()) {
				logger.debug("Se desconecto el cliente");
			}
		}
		logger.debug(bapiName + ": fin " + (new Date()));
		return resultMap;
	}
	
	/**
	 * Ejecuta una BAPI.
	 * 
	 * @param bapiName
	 *            nombre de la BAPI a ejecutar
	 * @param importMap
	 *            map con los import parameters, el key de las entradas del map
	 *            deben corresponder a los nombres declarados como Import en la
	 *            BAPI. Soporta elementos de datos y estructuras.
	 * @param destination
	 * 			indica en que ambiente se va a ejecutar la bapi
	 * @return 
	 * 			map con las estructuras y elementos de datos que devuelve la
	 * 			BAPI. El nombre de los campos que son enviados como elementos
	 * 			de datos no deben repetirse en los campos de alguna estructura
	 * 			por el manejo de los campos binarios(RAWSTRING)
	 */
	public Map execute(String bapiName, Map importMap){
		return execute(bapiName, importMap, false);
	}
	/*FIN EXECUTE2*/


	//RDM 22.09.2008	
	private Client getGenericUserClient(String user, String password){
		
		JCO.Client jcoClient = null;
		logger.debug("Creando el cliente JCO con el usuario generico " + user);		
		
		if (osname!=null && osname.startsWith(WINDOWS)) {
			//Sin Balanceo
			jcoClient = JCO.createClient(client, user, password, language, host, systemNumber);			
		} else {
			//Con Balanceo
			jcoClient = JCO.createClient(client, user, password, language, messageServer, r3Name, groupName);			
		}
		
		return jcoClient;
	}

	private Client getUserClient(BeanUsuario beanUsuario){		
		return getGenericUserClient(beanUsuario.getUsuario(),beanUsuario.getPassword());
	}
	

	/*
	 * (non-Javadoc)
	 * @see com.ticorp.framework.service.BapiExecutionService#getTableAsList(java.lang.String, java.util.Map, java.lang.String)
	 */
	public List getTableAsList(String bapiName, Map importMap, String tableName)
			throws BapiException {
		Map result = execute(bapiName, importMap, false);
		List resultList = result.get(tableName)==null?new ArrayList():(List)result.get(tableName);
		return resultList;
	}

	/**
	 * @param field
	 * @param value
	 * @throws ParseException
	 */
	private void putValueInField(Field field, Object value) {
		try {
			if (field != null && value != null) {
				if (StringUtils.equals(field.getTypeAsString(), DATE)
						&& StringUtils.isNotEmpty(value.toString())) {
					field.setValue(DateUtil.convertStringToDate(value.toString()));
				} else if (StringUtils.equals(field.getTypeAsString(), CHAR)) {
					field.setValue(value.toString());
				} else {
					field.setValue(value);
				}
			}
		} catch (Exception e) {
			logger.error("Ocurrio un error al intentar poner el valor del Map en el JCO.Field: " + e.getClass());
			logger.error("field.getTypeAsString()=" + field.getTypeAsString());
			logger.error("value.toString()=" + value);
			logger.error("error:",e);
		}
	}

	/**
	 * @param rowMap
	 * @param field
	 */
	private void putFieldInMap(Map rowMap, JCO.Field field, boolean convertVal) {
		try {

			if (StringUtils.equals(field.getTypeAsString(), CHAR)) {
				if(convertVal){
					if(((String)field.getValue()).equals("PEN")){
						field.setValue(Constantes.SIMBOLO_SOLES);
					}else if(((String)field.getValue()).equals("USD")){
						field.setValue(Constantes.SIMBOLO_DOLARES);
					}
				}
				rowMap.put(field.getName(), (String) field.getValue());
			} else if (StringUtils.equals(field.getTypeAsString(), DATE)) {
				rowMap.put(field.getName(), DateUtil.getDate((Date) field
						.getValue()));
			} else if (StringUtils.equals(field.getTypeAsString(), TIME)) {
				rowMap.put(field.getName(), DateUtil.getTime((Date) field
						.getValue()));
			} else if (StringUtils.equals(field.getTypeAsString(), BCD)) {
				rowMap.put(field.getName(), new BigDecimal((String) field
						.getValue()));
			} else {
				rowMap.put(field.getName(), field.getValue());
			}
		} catch (Exception e) {
			logger.error("Ocurrio un error al intentar poner el valor del JCO.Field en el Map: " + e.getClass());
			logger.error("field.getTypeAsString()=" + field.getTypeAsString());
			logger.error("value.toString()=" + field.getValue());
		}
	}

	public void finalize() {
	}

	public void setClient(String client) {
		this.client = client;
	}

	public void setHost(String host) {
		this.host = host;
	}
	
	public void setSystemNumber(String systemNumber) {
		this.systemNumber = systemNumber;
	}
	
	public void setLanguage(String language) {
		this.language = language;		
	}

	public void setHostNW_DEV(String hostNW_DEV) {
		this.hostNW_DEV = hostNW_DEV;		
	}
	public void setClientNW_DEV(String clientNW_DEV) {
		this.clientNW_DEV = clientNW_DEV;		
	}
	public void setSysNumberNW_DEV(String sysNumberNW_DEV) {
		this.sysNumberNW_DEV = sysNumberNW_DEV;		
	}

	public void setHostNW_PRD(String hostNW_PRD) {
		this.hostNW_PRD = hostNW_PRD;		
	}
	public void setClientNW_PRD(String clientNW_PRD) {
		this.clientNW_PRD = clientNW_PRD;		
	}
	public void setSysNumberNW_PRD(String sysNumberNW_PRD) {
		this.sysNumberNW_PRD = sysNumberNW_PRD;		
	}

	public void setMessageServer(String messageServer) {
		this.messageServer = messageServer;		
	}
	public void setGroupName(String groupName) {
		this.groupName = groupName;		
	}
	public void setR3Name(String r3Name) {
		this.r3Name = r3Name;		
	}

	public void setUserPRD(String userPRD) {
		BapiExecutionServiceImpl.userPRD = userPRD;
	}

	public void setPasswordPRD(String passwordPRD) {
		BapiExecutionServiceImpl.passwordPRD = passwordPRD;
	}
	
	public void setUser(String user) {
		String osname = null;
		try {
			osname = System.getProperty("os.name");
		} catch (Exception e) {
			osname = null;
		}
		
		if (osname!=null && osname.startsWith(WINDOWS)) {
			BapiExecutionServiceImpl.user = user;
		} else {
			BapiExecutionServiceImpl.user = userPRD;
		}
	}
	
	public void setPassword(String password) {		
		String osname = null;
		try {
			osname = System.getProperty("os.name");
		} catch (Exception e) {
			osname = null;
		}
		
		if (osname!=null && osname.startsWith(WINDOWS)) {
			BapiExecutionServiceImpl.password = password;
		} else {
			BapiExecutionServiceImpl.password = passwordPRD;
		}
	}

	

}
